//Date: 2023-12-20 23:01:27
//LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
//LastEditTime: 2023-12-25 22:52:48
//FilePath: /miniServiceForWin/src/websocket/websocketService.go

package websoket

import (
	"encoding/json"
	"fmt"
	"log"
	"log/slog"
	"miniServiceForWin/router"
	"miniServiceForWin/service"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

type CommandClass int

const (
	ShutDown CommandClass = iota
	Sleep
	ConstantSleep
	Restart
	MemLinkCode
	ShowMsg
)

type ExecCommand struct {
	Status  int          `json:"status"`
	Msg     string       `json:"msg"`
	Code    string       `json:"code"`
	Command CommandClass `json:"command"`
}

var (
	publicPublisher = service.NewPublisher()
)

func init() {
	r := router.GetMainRouter()

	r.StaticFile("/remote_mini_service/options/post_cmd_page", "/root/code/src/miniServiceForWin/src/static/request.html")
	// r.POST("/remote_mini_service/options/post_cmd_side", postCmd)
	r.POST("/remote_mini_service/options/post_cmd_from", postCmdFrom)

	r.GET("/remote_mini_service/options/execside/ws", wsHandler)
}

//description: 提供from表单提交命令的方式
//param {*gin.Context} c
//return {*}

func postCmdFrom(c *gin.Context) {
	port := c.GetHeader("X-Forwarded-Port")
	if port == "" {
		slog.Info("func post_cmd_from: con not get port data")
		c.JSON(http.StatusBadRequest, gin.H{
			"state": 400,
			"msg":   "bad request",
		})

		return
	}

	ip := c.GetHeader("X-Real-IP")
	if ip == "" {
		slog.Info("func post_cmd_from can not get ip data")
		c.JSON(http.StatusBadRequest, gin.H{
			"state": 400,
			"msg":   "bad request",
		})

		return
	}

	slog.Info(fmt.Sprintf("func postCmd:ip:%v port:%v send msg", ip, port))

	theCode := c.PostForm("code")
	if !service.IsCmdCodeValid(theCode) {
		slog.Info("func post_cmd_from con not pass the valid chack, the code is: " + theCode)
		c.JSON(http.StatusBadRequest, gin.H{
			"state": 400,
			"msg":   "bad request",
		})

		return
	}

	formCmd := c.PostForm("cmd")
	if formCmd == "" {
		slog.Info("func post_cmd_from can not get cmd data from form:" + formCmd)
		c.JSON(http.StatusBadRequest, gin.H{
			"state": 400,
			"msg":   "bad request",
		})

		return
	}

	cmd, err := strconv.Atoi(formCmd)
	if err != nil {
		slog.Info("func post_cmd_from, convert form command data failed")
		c.JSON(http.StatusBadRequest, gin.H{
			"state": 400,
			"msg":   "bad request",
		})

		return
	}

	switch cmd {
	case int(ShutDown), int(Sleep), int(ConstantSleep), int(Restart), int(ShowMsg):
		execCmdStuct := ExecCommand{Status: 200, Msg: "", Code: theCode, Command: CommandClass(cmd)}
		jsonStr, err := json.Marshal(execCmdStuct)
		if err != nil {
			slog.Info("func post_cmd_from can not convert cmd struct to json str")
			c.JSON(http.StatusBadRequest, gin.H{
				"state": 500,
				"msg":   "bad gayway",
			})

			return
		}

		key, exist := service.GetWinLinIPPortByCode(theCode)
		if !exist {
			slog.Info("func post_cmd_from, GetWinLinIPPortByCode failed code is :" + theCode)
			c.JSON(http.StatusBadRequest, gin.H{
				"state": 400,
				"msg":   "bad request",
			})

			return
		}
		publicPublisher.Publish(key, string(jsonStr))

		slog.Info("func post_cmd_from post success, key is: " + key + " cmd is: " + string(jsonStr))
		publicPublisher.PrintSubscribersInfo()

	default:
		slog.Info("func post_cmd_from cmd have no case in CommandClass")
	}
}

//description: 处理和winlink之间的逻辑
//param {*gin.Context} c
//return {*}

func wsHandler(c *gin.Context) {
	port := c.GetHeader("X-Forwarded-Port")
	if port == "" {
		c.JSON(http.StatusBadRequest, gin.H{
			"state": 400,
			"msg":   "bad request",
		})

		return
	}

	ip := c.GetHeader("X-Real-IP")
	if ip == "" {
		c.JSON(http.StatusBadRequest, gin.H{
			"state": 400,
			"msg":   "bad request",
		})

		return
	}

	slog.Info(fmt.Sprintf("func wsHandler:try to conn %v:%v", ip, port))

	conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		log.Println("Error upgrading connection: ", err)
		return
	}
	defer conn.Close()

	// 进入登录流程
	slog.Info(fmt.Sprintf("func wsHandler:in login proc %v:%v", ip, port))

	// 已经登录过，返回code
	if service.IsWinLinkLogined(ip, port) {
		res := ExecCommand{201, "have logged", service.GetLinkedWinLinkCode(ip, port), MemLinkCode}
		byteres, err := json.Marshal(res)
		if err != nil {
			slog.Error(fmt.Sprintf("can not pharse obj to json str:%v", service.GetLinkedWinLinkCode(ip, port)))
		}

		conn.WriteMessage(websocket.TextMessage, byteres)
	} else {
		// 注册并返回code
		code := service.WinLinkLoggin(ip, port)

		res := ExecCommand{200, "login", code, MemLinkCode}
		byteres, err := json.Marshal(res)
		if err != nil {
			slog.Error(fmt.Sprintf("can not pharse obj to json str:%v", res))
		}

		conn.WriteMessage(websocket.TextMessage, byteres)
	}
	defer service.WinLinkLoggout(ip, port)

	go hearBeatSend(conn)

	subchan := publicPublisher.Subscribe(ip + ":" + port)
	defer publicPublisher.UnSubscribe(ip + ":" + port)

	for com := range subchan {
		// messageType, message, err := conn.ReadMessage()
		// if err != nil {
		// 	slog.Error("Error reading message: ", err)
		// 	return
		// }

		// fmt.Println("Received message:", string(message))

		err = conn.WriteMessage(websocket.TextMessage, []byte(com))
		if err != nil {
			slog.Error("Error sending message: ", err)
			continue
		}
	}
}

func hearBeatSend(conn *websocket.Conn) {
	// heartbeatInterval 定义了心跳消息的发送间隔
	const heartbeatInterval = 6 * time.Second
	ticker := time.NewTicker(heartbeatInterval)
	defer ticker.Stop()

	for range ticker.C {
		if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
			log.Println("func hearBeatSend err", err)
			return
		}

		// slog.Info(fmt.Sprintf("heat beat send to %v", conn.RemoteAddr().String()))
	}
}
